
#include "DeleDAPI.h"

#include "xmlscanner.h"
#include "xmlutil.h"


#define PR_GETMEM               0   //  used to know how long is the string I want to obtain from DeleD,
                                    //  or better how much memory I need to allocate for getting data
#define PR_GETDATA              1   //  used to retrieve data from DeleD
#define PR_SETDATA              2   //  used to pass data to DeleD

#define PE_NOERROR              0   //  no errors in callback
#define PE_NODATA               1   //  no data to set or get
#define PE_INVALIDVERSION       2   //  invalid version
#define PE_INVALIDMEM           3   //  invalid memory size
#define PE_INVALIDGET           4   //  invalid get operation
#define PE_INVALIDSET           5   //  invalid set operation
#define PE_INVALIDHEADER        6   //  invalid header
#define PE_INVALIDDATASTRING    7   //  invalid data string
#define PE_INVALIDDATAPROPERTY  8   //  invalid data property
#define PE_OBJECTDATA           11  //  invalid object data
#define PE_LIGHTDATA            12  //  invalid light data
#define PE_MATERIALDATA         13  //  invalid material data
#define PE_UNKNOWNREASON        98  //  callback reason unknown
#define PE_UNKNOWNERROR         99  //  unknown error

#include <vector>

static struct {
  uint error;
  char const * str;
}
gErrors[] = {
  { PE_NOERROR, 0 },
  { PE_NODATA, "No data available." },
  { PE_INVALIDVERSION, "The plug-in does not support this version of DeleD." },
  { PE_INVALIDMEM, "The amount of memory supplied was not sufficient." },
  { PE_INVALIDGET, "The get operation is not supported." },
  { PE_INVALIDSET, "The set operation is not supported." },
  { PE_INVALIDHEADER, "The header is mal-formed." },
  { PE_INVALIDDATASTRING, "The data string is mal-formed." },
  { PE_INVALIDDATAPROPERTY, "The data property is mal-formed." },
  { PE_OBJECTDATA, "The data supplied for an object is mal-formed." },
  { PE_LIGHTDATA, "The data supplied for a light is mal-formed." },
  { PE_MATERIALDATA, "The data supplied for a material is mal-formed." },
  { PE_UNKNOWNREASON, "The reason for the callback is not supported." },
  { PE_UNKNOWNERROR, "The cause of the error is shrouded in mystery." },
};

char const * DeleDError(uint error)
{
  for (size_t i = 0; i < sizeof(gErrors)/sizeof(gErrors[0]); ++i) {
    if (error == gErrors[i].error) {
      return gErrors[i].str;
    }
  }
  return "The Gods are against us. We cannot prevail.";
}

#if 0

#include <windows.h>

#include <stdarg.h>

class Timer {
  public: 
    Timer() {
      long long pf;
      ::QueryPerformanceFrequency((LARGE_INTEGER*)&pf);
      mult_ = 1.0/(double)pf;
      ::QueryPerformanceCounter((LARGE_INTEGER*)&base_);
    }
    double Now() {
      long long pc;
      ::QueryPerformanceCounter((LARGE_INTEGER*)&pc);
      return double(pc-base_) * mult_;
    }
    long long base_;
    double mult_;
};
Timer timer;

static double Now()
{
  return timer.Now();
}

class LogFileHandle {
  public:
    LogFileHandle() {
      logFile_ = fopen("c:\\logfile.txt", "wb");
    }
    ~LogFileHandle() {
      fclose(logFile_);
    }
    FILE * logFile_;
};
LogFileHandle lfh;

static void Log(char const * fmt, ...)
{
  va_list vl;
  va_start(vl, fmt);
  vfprintf(lfh.logFile_, fmt, vl);
  va_end(vl);
}
#else

#define Log (void)
#define Now() 0

#endif

namespace DeleD {

  template<typename T>
  class ItfImpl : public IInterface<T>
  {
    public:
      ~ItfImpl()
      {
        if (vec_.size()) {
          destroy(&vec_[0]);
        }
      }
      virtual size_t count()
      {
        return vec_.size();
      }
      virtual T const * begin()
      {
        return vec_.size() ? &(*vec_.begin()) : 0;
      }
      virtual T const * end()
      {
        return vec_.size() ? &(*(vec_.begin() + vec_.size())) : 0;
      }
      virtual T const & item(size_t ix)
      {
        assert(ix < vec_.size());
        return vec_[ix];
      }

      template<typename Q>
      void destroy(Q ** pptr)
      {
        //  delete the pointers
        for (std::vector<T>::iterator ptr = vec_.begin(), end = vec_.end();
            ptr != end; ++ptr) {
          delete *ptr;
        }
      }
      template<typename P>
      void destroy(P * pptr)
      {
        pptr = 0;
        //  do nothing
      }
      std::vector<T> vec_;
  };
  
  class Scene : public IScene
  {
    public:
      Scene(TCallback cb) : cb_(cb)
      {
        buffer_ = tempBuffer_;
        bufferSize_ = sizeof(tempBuffer_);
        end_ = 0;
      }
      ~Scene()
      {
        if (buffer_ != tempBuffer_) {
          delete[] buffer_;
        }
      }
      virtual void dispose()
      {
        delete this;
      }
      virtual char const * name()
      {
        return name_.c_str();
      }
      virtual IInterface<IObject *> * objects()
      {
        return &objects_;
      }
      virtual IInterface<IMaterial *> * materials()
      {
        return &materials_;
      }
      virtual IInterface<ILight *> * lights()
      {
        return &lights_;
      }

      char * buffer(size_t size = 0)
      {
        if (size > bufferSize_) {
          if (buffer_ != tempBuffer_) {
            delete[] buffer_;
          }
          buffer_ = new char[size];
          end_ = 0;
        }
        return buffer_;
      }
      char * end()
      {
        assert(end_ != 0);
        return end_;
      }
      uint bufferSize()
      {
        return (uint)bufferSize_;
      }
      void op(char const * str)
      {
        Log("Beginning op(%s): %g\n", str, Now());
        end_ = 0;
        TCbData cbdata(PR_GETMEM);
        cbdata.data_ = str;
        cbdata.response_ = buffer(10000);
        cbdata.size_ = 10000;
        uint error = (*cb_)(&cbdata);
        if (error != 0) {
          throw DeleDError(error);
        }
        if (cbdata.size_ == 0) {
          end_ = buffer_;
          Log("No data in op(%s): %g\n", str, Now());
          return;
        }
        cbdata.data_ = str;
        cbdata.response_ = buffer(cbdata.size_+1);
        cbdata.response_[cbdata.size_] = 0;
        cbdata.reason_ = PR_GETDATA;
        error = (*cb_)(&cbdata);
        if (error) {
          throw DeleDError(error);
        }
        end_ = cbdata.size_ + buffer_;
        Log("Ending op(%s): %g\n", str, Now());
      }

      void readLights();
      void readMaterials();
      void readObjects();

      std::string           name_;
      ItfImpl<IObject *>    objects_;
      ItfImpl<IMaterial *>  materials_;
      ItfImpl<ILight *>     lights_;

      TCallback             cb_;

      char                  tempBuffer_[128];
      char *                buffer_;
      char *                end_;
      size_t                bufferSize_;
  };

  class Light : public ILight {
    public:
      Light()
      {
        //  I do some skanky casts in the storage below, so 
        //  make sure that my assumptions about memory layout 
        //  are correct.
        assert(sizeof(PointLight) == sizeof(DirectionalLight));
        assert(sizeof(DirectionalLight) == sizeof(SpotLight));
        assert(&((PointLight*)0)->center == &((DirectionalLight*)0)->center);
        assert(&((PointLight*)0)->center == &((SpotLight*)0)->center);
        assert(&((PointLight*)0)->id == &((DirectionalLight*)0)->id);
        assert(&((PointLight*)0)->id == &((SpotLight*)0)->id);
        memset(&light_, 0, sizeof(light_));
      }
      virtual char const *name() {
        return name_.c_str();
      }
      virtual char const *tag() {
        return tag_.c_str();
      }
      virtual LightType type()
      {
        return type_;
      }
      virtual PointLight const & point()
      {
        return light_;
      }
      virtual DirectionalLight const & directional()
      {
        return (DirectionalLight const &)light_;
      }
      virtual SpotLight const & spot()
      {
        return (SpotLight const &)light_;
      }

      std::string name_;
      std::string tag_;
      LightType type_;
      PointLight light_;
  };

  class Material : public IMaterial
  {
    public:
      Material()
        : id_(0)
      {
      }
      ~Material()
      {
      }
      virtual IInterface<ILayer *> * layers()
      {
        return &layers_;
      }
      virtual int id()
      {
        return id_;
      }
      virtual char const * category()
      {
        return category_.c_str();
      }
      virtual char const * name()
      {
        return name_.c_str();
      }
      virtual char const *tag()
      {
        return tag_.c_str();
      }
      virtual char const *comment()
      {
        return comment_.c_str();
      }

      ItfImpl<ILayer *> layers_;
      int id_;
      std::string name_;
      std::string category_;
      std::string tag_;
      std::string comment_;
  };

  class Layer : public ILayer
  {
    public:
      Layer()
        : type_(0)
        , blend_(0)
        , color_(0)
      {
      }
      //  0: texture
      //  1: color
      virtual int type()
      {
        return type_;
      }
      virtual char const * texture()
      {
        return texture_.c_str();
      }
      //  0xaabbggrr format
      virtual unsigned int color()
      {
        return color_;
      }
      //  0: inactive
      //  1: replace
      //  2: add
      //  3: modulate
      //  4: alphablend
      virtual int blend()
      {
        return blend_;
      }
      int type_;
      int blend_;
      unsigned int color_;
      std::string texture_;
  };

  class Object : public IObject
  {
    public:
      Object()
      {
      }
      ~Object()
      {
      }
      virtual char const * name()
      {
        return name_.c_str();
      }
      virtual char const * tag()
      {
        return tag_.c_str();
      }
      virtual IInterface<Float3> * vertices()
      {
        return &vertices_;
      }
      virtual IInterface<Poly> * polygons()
      {
        return &polygons_;
      }

      std::string name_;
      std::string tag_;
      int id_;
      int type_;
      int visible_;
      int snapmethod_;
      int groupid_;
      int auto_uv_;

      ItfImpl<Float3> vertices_;
      ItfImpl<Poly> polygons_;
  };
}

using namespace DeleD;


AutoDispose<IScene> NewScene(TCallback cb)
{
  Scene * s = new Scene(cb);
  s->readLights();
  s->readMaterials();
  s->readObjects();
  return AutoDispose<IScene>(s);
}

static void ScanColor(XMLNode *node, Color &c) {
  unsigned char *fp = &c.r_;
  while (node) {
    XMLScanner::Token tok = node->token();
    if (tok.keyIs("r")) {
      XMLUtil::ToNumber(node, fp[0]);
    }
    else if (tok.keyIs("g")) {
      XMLUtil::ToNumber(node, fp[1]);
    }
    else if (tok.keyIs("b")) {
      XMLUtil::ToNumber(node, fp[2]);
    }
    else if (tok.keyIs("a")) {
      XMLUtil::ToNumber(node, fp[3]);
    }
    node = node->next();
  }
}

static void ScanPosition(XMLNode *node, Float3 &pos) {
  float *fp = &pos.x_;
  while (node) {
    XMLScanner::Token tok = node->token();
    if (tok.keyIs("x")) {
      XMLUtil::ToNumber(node, fp[0]);
    }
    else if (tok.keyIs("y")) {
      XMLUtil::ToNumber(node, fp[1]);
    }
    else if (tok.keyIs("z")) {
      XMLUtil::ToNumber(node, fp[2]);
    }
    node = node->next();
  }
}

void Scene::readLights()
{
  Log("Beginning readLights(): %g\n", Now());
  op("<request><lights subset=\"all\" /></request>");
  XMLDom dom(buffer(), end()-buffer(), 0);
  XMLNode *lights = dom.root()->child(0);
  while (lights) {
    XMLScanner::Token tok = lights->token();
    if (tok.keyIs("lights")) {
      XMLNode *light = lights->child(0);
      while (light) {
        tok = light->token();
        if (tok.keyIs("light")) {
          XMLNode *param = light->child(0);
          Light *l = new Light();
          while (param) {
            tok = param->token();
            if (tok.keyIs("id")) {
              XMLUtil::ToNumber(param, l->light_.id);
            }
            else if (tok.keyIs("type")) {
              if (tok.valueIs("directional")) {
                l->type_ = LT_DIRECTIONALLIGHT;
              }
              else if (tok.valueIs("spot")) {
                l->type_ = LT_SPOTLIGHT;
              }
              else {  //  assume they're all point lights if unknown type
                l->type_ = LT_POINTLIGHT;
              }
            }
            else if (tok.keyIs("name")) {
              XMLUtil::ToText(param, l->name_);
            }
            else if (tok.keyIs("range")) {
              XMLUtil::ToNumber(param, l->light_.range);
            }
            else if (tok.keyIs("active")) {
              bool b = true;
              XMLUtil::ToBool(param, b);
              l->light_.active = b ? 1 : 0;
            }
            else if (tok.keyIs("intensity")) {
              XMLUtil::ToNumber(param, l->light_.intensity);
            }
            else if (tok.keyIs("tag")) {
              XMLUtil::ToText(param, l->tag_);
            }
            else if (tok.keyIs("diffuse")) {
              ScanColor(param->child(0), l->light_.diffuse);
            }
            else if (tok.keyIs("specular")) {
              ScanColor(param->child(0), l->light_.specular);
            }
            else if (tok.keyIs("position")) {
              ScanPosition(param->child(0), l->light_.center);
            }
            else if (tok.keyIs("attenuation")) {
              XMLNode *at = param->child(0);
              while (at) {
                tok = at->token();
                if (tok.keyIs("constant")) {
                  XMLUtil::ToNumber(at, l->light_.constant);
                }
                else if (tok.keyIs("linear")) {
                  XMLUtil::ToNumber(at, l->light_.linear);
                }
                else if (tok.keyIs("quadratic")) {
                  XMLUtil::ToNumber(at, l->light_.quadratic);
                }
                at = at->next();
              }
            }
            else if (tok.keyIs("direction")) {
              ScanPosition(param->child(0), l->light_._reserved1);
            }
            else if (tok.keyIs("cone")) {
              XMLNode *at = param->child(0);
              while (at) {
                tok = at->token();
                if (tok.keyIs("inner")) {
                  XMLUtil::ToNumber(at, l->light_._reserved2);
                }
                else if (tok.keyIs("outer")) {
                  XMLUtil::ToNumber(at, l->light_._reserved3);
                }
                else if (tok.keyIs("falloff")) {
                  XMLUtil::ToNumber(at, l->light_._reserved4);
                }
                at = at->next();
              }
            }
            param = param->next();
          }
        }
        light = light->next();
      }
      break;
    }
    lights = lights->next();
  }


  Log("Ending readLights(): %g\n", Now());
}

void Scene::readMaterials()
{
  Log("Beginning readMaterials(): %g\n", Now());
  op("<request><materials subset=\"all\" /></request>");
  XMLDom dom(buffer(), end()-buffer(), 0);

  materials_.vec_.resize(0);
  XMLNode *root = dom.root();
  XMLNode *coll = root->child(0);
  while (coll) {
    XMLScanner::Token const &tok = coll->token();
    if (tok.keyIs("materials")) {
      XMLNode *cat = coll->child(0);
      while (cat) {
        XMLScanner::Token const &ctok = cat->token();
        if (ctok.keyIs("category")) {
          XMLNode *mtl = cat->child(0);
          std::string catName;
          while (mtl) {
            XMLScanner::Token const &mtok = mtl->token();
            if (mtok.keyIs("name")) {
              XMLUtil::ToText(mtl, catName);
            }
            else if (mtok.keyIs("material")) {
              Material * m = new Material();
              m->category_ = catName;
              XMLNode *mprop = mtl->child(0);
              while (mprop) {
                XMLScanner::Token const &prop = mprop->token();
                if (prop.keyIs("id")) {
                  XMLUtil::ToNumber(mprop, m->id_);
                }
                else if (prop.keyIs("name")) {
                  XMLUtil::ToText(mprop, m->name_);
                }
                else if (prop.keyIs("layer")) {
                  Layer * l = new Layer();
                  m->layers_.vec_.push_back(l);
                  XMLNode *lay = mprop->child(0);
                  while (lay) {
                    XMLScanner::Token const &ltok = lay->token();
                    if (ltok.keyIs("type")) {
                      l->type_ = 1;
                      if (ltok.valueIs("texture")) {
                        l->type_ = 0;
                      }
                      else if (ltok.valueIs("color")) {
                        l->type_ = 1;
                      }
                      else if (ltok.valueIs("lightmap")) {
                        l->type_ = 2;
                      }
                    }
                    else if (ltok.keyIs("blend")) {
                      l->blend_ = 0;
                      if (ltok.valueIs("inactive")) {
                        l->blend_ = 0;
                      }
                      else if (ltok.valueIs("replace")) {
                        l->blend_ = 1;
                      }
                      else if (ltok.valueIs("add")) {
                        l->blend_ = 2;
                      }
                      else if (ltok.valueIs("modulate")) {
                        l->blend_ = 3;
                      }
                      else if (ltok.valueIs("alphablend")) {
                        l->blend_ = 4;
                      }
                    }
                    else if (ltok.keyIs("texture")) {
                      XMLNode *tex = lay->child(0);
                      while (tex) {
                        XMLScanner::Token const &ttok = tex->token();
                        if (ttok.keyIs("file")) {
                          XMLUtil::ToText(tex, l->texture_);
                          break;
                        }
                        tex = tex->next();
                      }
                    }
                    else if (ltok.keyIs("color")) {
                      XMLNode *col = lay->child(0);
                      while (col) {
                        XMLScanner::Token const &ttok = col->token();
                        if (ttok.keyIs("r")) {
                          int r;
                          XMLUtil::ToNumber(col, r);
                          l->color_ = (l->color_ & 0xffffff00) | (r & 0xff);
                        }
                        else if (ttok.keyIs("g")) {
                          int r;
                          XMLUtil::ToNumber(col, r);
                          l->color_ = (l->color_ & 0xffff00ff) | ((r<<8) & 0xff00);
                        }
                        else if (ttok.keyIs("b")) {
                          int r;
                          XMLUtil::ToNumber(col, r);
                          l->color_ = (l->color_ & 0xff00ffff) | ((r<<16) & 0xff0000);
                        }
                        else if (ttok.keyIs("a")) {
                          int r;
                          XMLUtil::ToNumber(col, r);
                          l->color_ = (l->color_ & 0x00ffffff) | ((r<<24) & 0xff000000);
                        }
                        col = col->next();
                      }
                    }
                    lay = lay->next();
                  }
                }
                mprop = mprop->next();
              }
              while ((int)materials_.vec_.size() <= (int)m->id_) {
                materials_.vec_.push_back(0);
              }
              materials_.vec_[m->id_] = m;
            }
            mtl = mtl->next();
          }
        }
        cat = cat->next();
      }
      break;
    }
    coll = coll->next();
  }

  Log("Ending readMaterials(): %g\n", Now());
}

void Scene::readObjects()
{
  //  The way I'm parsing here, I'm actually just scanning forward. 
  //  I might as well use the xml scanner, instead of the dom. Although 
  //  the difference in runtime is small, except for the largest scenes.

  Log("Beginning readObjects(): %g\n", Now());
  op("<request><primitives subset=\"all\" /></request>");
  XMLDom dom(buffer(), end()-buffer(), 0);
  XMLNode *scene = dom.root();
  XMLNode *primitives = scene->child(0);
  while (primitives) {
    XMLScanner::Token ptok = primitives->token();
    if (ptok.keyIs("primitives") ) {
      XMLNode *primitive = primitives->child(0);
      while (primitive) {
        XMLScanner::Token prtok = primitive->token();
        if (prtok.keyIs("primitive")) {
          Object *o = new Object();
          XMLNode *vertices = primitive->child(0);
          XMLNode *polygons = vertices;
          while (vertices) {
            XMLScanner::Token vtok = vertices->token();
            if (vtok.keyIs("tag")) {
              XMLUtil::ToText(vertices, o->tag_);
            }
            else if (vtok.keyIs("autoUV")) {
              bool b = false;
              XMLUtil::ToBool(vertices, b);
              o->auto_uv_ = b ? 1 : 0;
            }
            else if (vtok.keyIs("id")) {
              XMLUtil::ToNumber(vertices, o->id_);
            }
            else if (vtok.keyIs("name")) {
              XMLUtil::ToText(vertices, o->name_);
            }
            else if (vtok.keyIs("vertices")) {
              XMLNode *vertex = vertices->child(0);
              while (vertex) {
                XMLScanner::Token vttok = vertex->token();
                if (vttok.keyIs("vertex")) {
                  XMLNode *comp = vertex->child(0);
                  Float3 vtx(0, 0, 0);
                  while (comp) {
                    XMLScanner::Token vcomp = comp->token();
                    if (vcomp.keyIs("x")) {
                      XMLUtil::ToNumber(comp, vtx.x_);
                    }
                    else if (vcomp.keyIs("y")) {
                      XMLUtil::ToNumber(comp, vtx.y_);
                    }
                    else if (vcomp.keyIs("z")) {
                      XMLUtil::ToNumber(comp, vtx.z_);
                    }
                    comp = comp->next();
                  }
                  o->vertices_.vec_.push_back(vtx);
                }
                vertex = vertex->next();
              }
              polygons = vertices->next();
              break;
            }
            vertices = vertices->next();
          }
          while (polygons) {
            XMLScanner::Token ptok = polygons->token();
            if (ptok.keyIs("polygons")) {
              XMLNode *poly = polygons->child(0);
              while (poly) {
                XMLScanner::Token pptok = poly->token();
                if (pptok.keyIs("poly")) {
                  XMLNode *cld = poly->child(0);
                  int mid = 0;
                  struct vx {
                    int vix;
                    float uv[3][2];
                  };
                  //  count how many uv we've seen
                  int nuv = 1;
                  std::vector<vx> pverts;
                  while (cld) {
                    XMLScanner::Token ctok = cld->token();
                    if (ctok.keyIs("mid")) {
                      //  get material
                      XMLUtil::ToNumber(cld, mid);
                    }
                    else if (ctok.keyIs("vertex")) {
                      //  get vertex
                      XMLNode *vnod = cld->child(0);
                      vx theVert;
                      memset(&theVert, 0, sizeof(theVert));
                      while (vnod) {
                        XMLScanner::Token stok = vnod->token();
                        if (stok.keyIs("vid")) {
                          XMLUtil::ToNumber(vnod, theVert.vix);
                          assert((size_t)theVert.vix < o->vertices_.vec_.size());
                        }
                        else if (stok.keyIs("u0")) {
                          XMLUtil::ToNumber(vnod, theVert.uv[0][0]);
                        }
                        else if (stok.keyIs("v0")) {
                          XMLUtil::ToNumber(vnod, theVert.uv[0][1]);
                        }
                        else if (stok.keyIs("u1")) {
                          XMLUtil::ToNumber(vnod, theVert.uv[1][0]);
                          nuv = std::max(nuv, 2);
                        }
                        else if (stok.keyIs("v1")) {
                          XMLUtil::ToNumber(vnod, theVert.uv[1][1]);
                          nuv = std::max(nuv, 2);
                        }
                        else if (stok.keyIs("u2")) {
                          XMLUtil::ToNumber(vnod, theVert.uv[2][0]);
                          nuv = std::max(nuv, 3);
                        }
                        else if (stok.keyIs("v2")) {
                          XMLUtil::ToNumber(vnod, theVert.uv[2][1]);
                          nuv = std::max(nuv, 3);
                        }
                        vnod = vnod->next();
                      }
                      pverts.push_back(theVert);
                    }
                    cld = cld->next();
                  }
                  //  Add a poly to the object -- this is not super efficient, as 
                  //  the array will be re-allocated based on how many polys there are.
                  o->polygons_.vec_.push_back(Poly());
                  Poly &pp = o->polygons_.vec_.back();
                  pp.material_ = mid;
                  uint *ipp = pp.indices_.rewrite(pverts.size());
                  FixedArray<Float2> *uva = pp.coords_.rewrite(nuv);
                  for (int i = 0; i < nuv; ++i) {
                    uva[i].rewrite(pverts.size());
                  }
                  for (size_t i = 0, e = pverts.size(); i < e; ++i) {
                    ipp[i] = pverts[i].vix;
                    for (int j = 0; j < nuv; ++j) {
                      uva[j].begin()[i].u_ = pverts[i].uv[j][0];
                      uva[j].begin()[i].v_ = pverts[i].uv[j][1];
                    }
                  }
                }
                poly = poly->next();
              }
              break;
            }
            polygons = polygons->next();
          }
          objects_.vec_.push_back(o);
        }
        primitive = primitive->next();
      }
    }
    primitives = primitives->next();
  }
  Log("Ending readObjects(): %g\n", Now());
}

